package route

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/cilium/ebpf/rlimit"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG  -cflags $BPF_CFLAGS bpf ../../ebpf/route.c  -- -I../../headers

// App stores ebpf programs and maps together with the desired state
const (
	progName      = "xdp/xdp_rtcache_prog"
	BlacklistPath = "/sys/fs/bpf"
)

type rtItem struct {
	Index int
	Smac  [6]byte
	Dmac  [6]byte
}

var (
	neighChan = make(chan netlink.NeighUpdate)
	routeChan = make(chan netlink.RouteUpdate)
	closeChan = make(chan struct{})
	logger    *logrus.Logger
)

func init() {
	logger = logrus.New()
	logger.SetFormatter(&logrus.TextFormatter{
		DisableTimestamp: true,
	})
}

// Hook provides a set of operations that allow for managing the execution of the XDP program
// including attaching it on the network interface, harvesting various statistics or removing
// the program from the interface.
type Hook struct {
	mod *bpfObjects
	//link link.Link
}

// NewHook constructs a new instance of the XDP hook from provided XDP code.
func NewHook() (*Hook, error) {
	// Allow the current process to lock memory for eBPF resources.
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatal(err)
		return nil, err
	}
	// Load pre-compiled programs and maps into the kernel.
	objs := bpfObjects{}
	//var opts ebpf.CollectionOptions
	if err := loadBpfObjects(&objs, nil); err != nil {
		log.Fatalf("loading objects: %v", err)
		return nil, err

	}

	return &Hook{mod: &objs}, nil
}

func (h *Hook) listenRoute(InNetIface string) error {
	// listen for the arp map
	if err := netlink.NeighSubscribe(neighChan, closeChan); err != nil {
		log.Fatalf("arp map subscribe err %v ", err)
	}
	// listen for the route list
	if err := netlink.RouteSubscribe(routeChan, closeChan); err != nil {
		log.Fatalf("route subscribe err %v", err)
	}

	go func() {
		for {
			select {
			case <-neighChan:
			case <-routeChan:
			}
			// delete the cached map
			var (
				entries = h.mod.bpfMaps.RtcacheMap.Iterate()
				key     uint32
				value   rtItem
			)
			for entries.Next(&key, &value) {
				h.mod.bpfMaps.RtcacheMap.Delete(key)
			}
			if err := entries.Err(); err != nil {
				log.Println("entries err ", err)
			}
			log.Println("arp map or router changed")
		}
	}()
	// load the network interface map
	var (
		ifaceList []netlink.Link
	)
	for _, value := range strings.Split(InNetIface, ",") {
		link, err := netlink.LinkByName(value)
		if err != nil {
			log.Println("network not found ", err)
			continue
		}
		ifaceList = append(ifaceList, link)
		ifindex := uint32(link.Attrs().Index)
		h.mod.IfDerect.Put(ifindex, ifindex)
		if err := netlink.LinkSetXdpFd(link, h.mod.XdpRtcacheProg.FD()); err != nil {
			log.Fatalf("link set xdp err %v", err)
		}
	}

	var signals = make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, syscall.SIGTERM)

	select {
	case <-signals:
	case <-closeChan:
	}
	for _, link := range ifaceList {
		netlink.LinkSetXdpFd(link, -1)
	}
	log.Println("ended")
	return nil
}

// func loadIProute2Bpf() (*ebpf.CollectionSpec, error) {
// 	reader := bytes.NewReader(_BpfBytes)
// 	spec, err := ebpf.LoadCollectionSpecFromReader(reader)
// 	if err != nil {
// 		return nil, fmt.Errorf("can't load bpf: %w", err)
// 	}
// 	for _, m := range spec.Maps {
// 		if m.Extra == nil || m.Extra.Len() == 0 {
// 			logger.Fatalf("Expected extra bytes in map %s", m.Name)
// 		}
// 		//fmt.Printf("have Extra %v", m.Extra)
// 		m.Extra = nil
// 	}
// 	return spec, err
// }
// func loadIProute2BpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
// 	spec, err := loadIProute2Bpf()
// 	if err != nil {
// 		logger.Fatal(err)
// 		return err
// 	}
// 	for _, mapSpec := range spec.Maps {
// 		mapSpec.Pinning = ebpf.PinByName
// 		opts.Maps.PinPath = BlacklistPath

// 	}

// 	return spec.LoadAndAssign(obj, opts)
// }

// Close closes the underlying eBPF module by disposing any allocated resources.
func (h *Hook) Close() error {
	err := h.mod.Close()
	if err != nil {
		return fmt.Errorf("close fail: %v", err)
	}
	return err
}
